<!DOCTYPE html>
<!--
Copyright (c) 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/tracing/extras/chrome/chrome_processes.html">
<link rel="import" href="/tracing/model/user_model/user_expectation.html">

<script>
'use strict';

tr.exportTo('tr.metrics.sh', function() {
  // Returns a weight for this score.
  // score should be a number between 0 and 1 inclusive.
  // This function is expected to be passed to tr.b.math.Statistics.weightedMean
  // as its weightCallback.
  function perceptualBlend(ir, index, score) {
    // Lower scores are exponentially more important than higher scores
    // due to the Peak-end rule.
    // Other than that general rule, there is no specific reasoning behind this
    // specific formula -- it is fairly arbitrary.
    return Math.exp(1 - score);
  }

  function filterExpectationsByRange(irs, opt_range) {
    const filteredExpectations = [];
    irs.forEach(function(ir) {
      if (!(ir instanceof tr.model.um.UserExpectation)) return;

      if (!opt_range ||
          opt_range.intersectsExplicitRangeInclusive(ir.start, ir.end)) {
        filteredExpectations.push(ir);
      }
    });
    return filteredExpectations;
  }

  /**
   * Splits the global memory dumps in |model| by browser name.
   *
   * @param {!tr.Model} model The trace model from which the global dumps
   *     should be extracted.
   * @param {!tr.b.math.Range=} opt_rangeOfInterest If provided, global memory
   *     dumps that do not inclusively intersect the range will be skipped.
   * @return {!Map<string, !Array<!tr.model.GlobalMemoryDump>} A map from
   *     browser names to the associated global memory dumps.
   */
  function splitGlobalDumpsByBrowserName(model, opt_rangeOfInterest) {
    const chromeModelHelper =
        model.getOrCreateHelper(tr.model.helpers.ChromeModelHelper);
    const browserNameToGlobalDumps = new Map();
    const globalDumpToBrowserHelper = new WeakMap();

    // 1. For each browser process in the model, add its global memory dumps to
    // |browserNameToGlobalDumps|. |chromeModelHelper| can be undefined if
    // it fails to find any browser, renderer or GPU process (see
    // tr.model.helpers.ChromeModelHelper.supportsModel).

    if (chromeModelHelper) {
      chromeModelHelper.browserHelpers.forEach(function(helper) {
        // Retrieve the associated global memory dumps and check that they
        // haven't been classified as belonging to another browser process.
        const globalDumps = skipDumpsThatDoNotIntersectRange(
            helper.process.memoryDumps.map(d => d.globalMemoryDump),
            opt_rangeOfInterest);
        globalDumps.forEach(function(globalDump) {
          const existingHelper = globalDumpToBrowserHelper.get(globalDump);
          if (existingHelper !== undefined) {
            throw new Error('Memory dump ID clash across multiple browsers ' +
                'with PIDs: ' + existingHelper.pid + ' and ' + helper.pid);
          }
          globalDumpToBrowserHelper.set(globalDump, helper);
        });

        makeKeyUniqueAndSet(browserNameToGlobalDumps,
            tr.e.chrome.chrome_processes.canonicalizeName(helper.browserName),
            globalDumps);
      });
    }

    // 2. If any global memory dump does not have any associated browser
    // process for some reason, associate it with an 'unknown_browser' browser
    // so that we don't lose the data.

    const unclassifiedGlobalDumps = skipDumpsThatDoNotIntersectRange(
        model.globalMemoryDumps.filter(g => !globalDumpToBrowserHelper.has(g)),
        opt_rangeOfInterest);
    if (unclassifiedGlobalDumps.length > 0) {
      makeKeyUniqueAndSet(
          browserNameToGlobalDumps, 'unknown_browser', unclassifiedGlobalDumps);
    }

    return browserNameToGlobalDumps;
  }

  /**
   * Function for adding entries with duplicate keys to a map without
   * overriding existing entries.
   *
   * This is achieved by appending numeric indices (2, 3, 4, ...) to duplicate
   * keys. Example:
   *
   *   const map = new Map();
   *   // map = Map {}.
   *
   *   makeKeyUniqueAndSet(map, 'key', 'a');
   *   // map = Map {"key" => "a"}.
   *
   *   makeKeyUniqueAndSet(map, 'key', 'b');
   *   // map = Map {"key" => "a", "key2" => "b"}.
   *                                ^^^^
   *   makeKeyUniqueAndSet(map, 'key', 'c');
   *   // map = Map {"key" => "a", "key2" => "b", "key3" => "c"}.
   *                                ^^^^           ^^^^
   */
  function makeKeyUniqueAndSet(map, key, value) {
    let uniqueKey = key;
    let nextIndex = 2;
    while (map.has(uniqueKey)) {
      uniqueKey = key + nextIndex;
      nextIndex++;
    }
    map.set(uniqueKey, value);
  }

  function skipDumpsThatDoNotIntersectRange(dumps, opt_range) {
    if (!opt_range) return dumps;
    return dumps.filter(d => opt_range.intersectsExplicitRangeInclusive(
        d.start, d.end));
  }

  /**
   * Returns true if |category| is one of the categories of |event|, and |event|
   * has title |title|.
   *
   * TODO(dproy): Make this a method on a suitable parent class of the
   * event/slice classes that are used with this function.
   */
  function hasCategoryAndName(event, category, title) {
    return event.title === title && event.category &&
        tr.b.getCategoryParts(event.category).includes(category);
  }

  return {
    hasCategoryAndName,
    filterExpectationsByRange,
    perceptualBlend,
    splitGlobalDumpsByBrowserName
  };
});
</script>
